package com.kbplus.demo.data.permission.config;

import cn.hutool.core.collection.CollectionUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
import com.kbplus.demo.data.permission.entity.LoginUser;
import com.kbplus.demo.data.permission.entity.Role;
import com.kbplus.demo.data.permission.mapper.CommonMapper;
import com.kbplus.demo.data.permission.utils.SpringUtils;
import com.kbplus.demo.data.permission.utils.TokenService;
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.select.*;
import net.sf.jsqlparser.util.TablesNamesFinder;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.springframework.beans.factory.annotation.Autowired;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Set;

/**
 * @author kbplus
 * @version v1.0
 * @date 2022-05-13 17:49:18
 */
public class DataPermissionInterceptor implements InnerInterceptor {

    @Autowired
    private TokenService tokenService;

    @Autowired
    private HttpServletRequest httpServletRequest;

    @Override
    public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql){
        String firstSql = boundSql.getSql();
        Field field = null;
        try {

            Select statement = (Select) CCJSqlParserUtil.parse(boundSql.getSql());
            if(!ifPage(parameter)){
                return;
            }

            LoginUser loginUser = tokenService.getLoginUser(httpServletRequest);
            if(loginUser==null){
                return;
            }

            Set<String> dataPermissions = loginUser.getDataPermissions();
            Set<Role> roles = loginUser.getRoles();
            for (Role role : roles) {
                if("admin".equals(role.getCode())){
                    return;
                }
            }


            List<String> mainTables = getMainTable(statement);
            field = boundSql.getClass().getDeclaredField("sql");
            field.setAccessible(true);

            if(dataPermissions==null || dataPermissions.contains(DataPermissionEnum.NONE.getCode())){
                String newSql = addWhereCondition(boundSql.getSql(), "1=2",DataPermissionEnum.NONE);
                //通过反射修改sql语句
                field.set(boundSql, newSql);
                System.out.println(newSql);
                return;
            }

            //获取公共mapper取字段是否存在
            CommonMapper commonMapper = SpringUtils.getBean(CommonMapper.class);

            String tenantTable = getFirstTableOnField("tenant_id", mainTables,commonMapper);
            if(tenantTable!=null) {
                //初始默认匹配租户id查询
                String tenantSql = addWhereCondition(boundSql.getSql(), tenantTable + ".tenant_id=" + loginUser.getTenantId(), DataPermissionEnum.NONE);
                //通过反射修改sql语句
                field.set(boundSql, tenantSql);
                System.out.println(tenantSql);
            }

            String organizationTable=null;
            if(dataPermissions.contains(DataPermissionEnum.DEPT.getCode())){

                organizationTable = getFirstTableOnField("create_user_organization_id", mainTables,commonMapper);
                if(organizationTable!=null) {
                    String newSql = addWhereCondition(boundSql.getSql(), organizationTable + ".create_user_organization_id=" + loginUser.getDeptId(), DataPermissionEnum.DEPT);
                    //通过反射修改sql语句
                    field.set(boundSql, newSql);
                    System.out.println(newSql);
                }
            }

            if(dataPermissions.contains(DataPermissionEnum.DEPT_CHILDREN.getCode())){

                if(organizationTable==null){
                    organizationTable = getFirstTableOnField("create_user_organization_id", mainTables,commonMapper);
                }
                if(organizationTable!=null) {

                    String newSql = addWhereCondition(boundSql.getSql(), getCondition(organizationTable, "create_user_organization_id", loginUser.getDeptChildren()), DataPermissionEnum.DEPT_CHILDREN);
                    //通过反射修改sql语句
                    field.set(boundSql, newSql);
                    System.out.println(newSql);
                }
            }

            if(dataPermissions.contains(DataPermissionEnum.POST.getCode())){

                String postTable = getFirstTableOnField("tenant_id", mainTables,commonMapper);
                if(postTable!=null) {

                    String sql = boundSql.getSql();
                    if (sql.contains("where") || sql.contains("WHERE")) {
                        sql = sql + " and ";
                    } else {
                        sql = sql + " where ";
                    }
                    String newSql = sql + getPositionCondition(postTable, "create_user_post_id", loginUser.getPostIds());
                    //通过反射修改sql语句
                    field.set(boundSql, newSql);
                    System.out.println(newSql);
                }
            }
            if(dataPermissions.contains(DataPermissionEnum.OWN.getCode())){

                String userTable = getFirstTableOnField("create_user", mainTables,commonMapper);
                if(userTable!=null) {
                    String newSql = addWhereCondition(boundSql.getSql(), userTable + ".create_user=" + loginUser.getUserId(), DataPermissionEnum.OWN);
                    //通过反射修改sql语句
                    field.set(boundSql, newSql);
                    System.out.println(newSql);
                }
            }
        } catch (JSQLParserException | NoSuchFieldException | IllegalAccessException e) {
            if(StringUtils.isNotEmpty(firstSql)&& ObjectUtils.isNotEmpty(field)){
                try {
                    field.set(boundSql, firstSql);
                } catch (IllegalAccessException illegalAccessException) {
                    illegalAccessException.printStackTrace();
                }
            }
            e.printStackTrace();
        }

    }

    /**
     * 获取拥有字段的指定第一张表
     *
     * @param field
     * @return
     */
    private String getFirstTableOnField(String field,List<String> tables,CommonMapper commonMapper) {
        if(CollectionUtil.isEmpty(tables))
            return null;
        for (String table : tables) {
            if(commonMapper.getFieldExists(table,field)>0)
                return table;
        }

        return null;
    }

    /**
     * 获取查询字段
     *
     * @param selectBody
     * @return
     */
    private List<SelectItem> getSelectItems(SelectBody selectBody) {
        if (selectBody instanceof PlainSelect) {
            return ((PlainSelect) selectBody).getSelectItems();
        }
        return null;
    }

    /**
     * 特殊处理 创建职业sql
     *
     * @param tableName 表名
     * @param fieldName 字段名
     * @param ids       值
     * @return
     */
    private String getPositionCondition(String tableName, String fieldName, Collection<String> ids) {

        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("(");
        for (String id : ids) {
            stringBuilder.append(tableName).append(".").append(fieldName).append(" like '%").append(id).append("%' or ");
        }
        stringBuilder.delete(stringBuilder.length()-3,stringBuilder.length()).append(")");

        return stringBuilder.toString();
    }

    /**
     * 生成where 条件字符串
     *
     * @param tableName 表名
     * @param fieldName 字段名
     * @param ids       值
     * @return
     */
    private String getCondition(String tableName, String fieldName, Collection<String> ids) {
        return tableName + "." + fieldName + " in (" + StringUtils.join(ids, ",") + ")";
    }

    /**
     * 获取tables的表名
     *
     * @param statement
     * @return
     */
    private List<String> getMainTable(Select statement) {
            TablesNamesFinder tablesNamesFinder = new TablesNamesFinder();
            return tablesNamesFinder.getTableList(statement);

    }

    /**
     * 判断是否分页
     *
     * @param selectBody
     * @return
     */
    private Limit ifPage(SelectBody selectBody) {
        if (selectBody instanceof PlainSelect) {
            return ((PlainSelect) selectBody).getLimit();
        }
        return null;
    }

    /**
     * 判断是否分页
     *
     * @param parameter
     * @return
     */
    private  boolean ifPage(Object parameter) {
        if(parameter instanceof String) return false;
        JSONObject jsonObject = JSON.parseObject(JSON.toJSONString(parameter));
        return jsonObject.containsKey("page") ||
                jsonObject.containsKey("size") ||
                jsonObject.containsKey("current");
    }



    /**
     * 在原有的sql中增加新的where条件
     *
     * @param sql       原sql
     * @param condition 新的and条件
     * @return 新的sql
     */
    private String addWhereCondition(String sql, String condition, DataPermissionEnum dataPermissionEnum, List<String> detps,Set<String> posts) {
        try {
            Select select = (Select) CCJSqlParserUtil.parse(sql);
            PlainSelect plainSelect = (PlainSelect) select.getSelectBody();
            final Expression expression = plainSelect.getWhere();
            final Expression envCondition = CCJSqlParserUtil.parseCondExpression(condition);
            if (Objects.isNull(expression)) {
                plainSelect.setWhere(envCondition);
            } else {
                if(DataPermissionEnum.NONE==dataPermissionEnum){
                    AndExpression andExpression = new AndExpression(expression, envCondition);
                    plainSelect.setWhere(andExpression);
                }else {
                    OrExpression orExpression = new OrExpression(expression, envCondition);
                    plainSelect.setWhere(orExpression);
                }

            }
            return plainSelect.toString();
        } catch (JSQLParserException e) {
            throw new RuntimeException(e);
        }
    }

    private String addWhereCondition(String sql, String condition, DataPermissionEnum dataPermissionEnum) {
        return addWhereCondition(sql,condition,dataPermissionEnum,null,null);
    }

    private String addWhereCondition(String sql, String condition, DataPermissionEnum dataPermissionEnum, List<String> detps) {
        return addWhereCondition(sql,condition,dataPermissionEnum,detps,null);
    }

    private String addWhereCondition(String sql, String condition, DataPermissionEnum dataPermissionEnum, Set<String> posts) {
        return addWhereCondition(sql,condition,dataPermissionEnum,null,posts);
    }

}
